Required packages

install.packages(c("tidyverse", "ggpubr", "readxl"))

Datsets

We will be using historical data on Uk election results. If time allows, we will explore the 2024 results

Outline

In this tutorial we will introduction / review some concepts of using dplyr, ggplot2 and other parts of the “tidyverse”. Briefly:-

For more information on these key packages you can consult the “cheatsheets” available through the help menus, or from the tidyverse website itself.

Get Started

Lets’ load the packages we will need. tidyverse will load all the packages we need

library(tidyverse)
library(readxl)

First attempt to read using the read_xlsx package - which is developed specifically for xls and xlsx files. Even though the function runs without error, it’s highly recommended to View the contents of the data frame.

raw <- read_xlsx("1918-2019election_results_by_pcon.xlsx")
View(raw)

There’s clearly some header information we need to deal with. The skip argument allows some rows in the input file to be ignored.

raw <- read_xlsx("1918-2019election_results_by_pcon.xlsx", skip = 3)
raw

And we need to select the particular sheet that we’re interested in

excel_sheets("1918-2019election_results_by_pcon.xlsx")
raw <- read_xlsx("1918-2019election_results_by_pcon.xlsx", skip = 3,sheet = 29)

Lets use dplyr to pick columns of interest. Some manual renaming involved :/

cleaned <- raw %>% 
  select(Constituency:Electorate, Turnout, `Total votes`, contains("Votes...")) %>% 
  rename("Conservative" = `Votes...8`,
         "Labour" = `Votes...11`,
         "Lib. Dem." = `Votes...14`,
         "Brexit" = `Votes...17`,
         "Green" = `Votes...20`,
         "SNP" = `Votes...23`,
         "Plaid Cymru" = `Votes...26`,
         "DUP" = `Votes...29`,
         "Sinn Fein" = `Votes...32`,
         "SDLP" = `Votes...35`,
         "UUP" = `Votes...38`,
         "Alliance" = `Votes...41`,
         "Other" = `Votes...44`)
cleaned

However, a bit of inspection shows some extra rows in the file that shouldn’t have been included.

tail(cleaned)

Such problematic rows can be identified by having an NA in the Country column

cleaned <- filter(cleaned, !is.na(Country))

We can save as csv file for future use.

write_csv(cleaned, "uk_election_2019.csv")

If at any point we need to read the original data we can do:-

cleaned <- read_csv("uk_election_2019.csv")
Rows: 650 Columns: 20
── Column specification ──────────────────────────────────────────────
Delimiter: ","
chr  (4): Constituency, County, Country/Region, Country
dbl (16): Electorate, Turnout, Total votes, Conservative, Labour, ...

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

As part of understanding the data it is helpful to carry out a few checks on the data quality. Although we have the total number of votes as a column, we can check that this has been computed correctly.

cleaned %>% 
  mutate(Total_votes_calc = Electorate * Turnout) %>%
  select(`Total votes`, Total_votes_calc)

We now have some data that we can start to visualise. For this we are going to use the ggplot2 package. This is a flexible and extendable plotting framework with a consistent interface. It can make publication-ready figures with less effort than “base” R. With some practice, you will spend less time writing code to make figures and more time interpreting and interrogating your data - which is what we really want to spend our time doing!

A useful resource to guide us in making our figures is data-to-viz. Sometimes it can even help to sketch a figure first using pencil and paper.

We start by looking at the size of electorate size (the number of people eligible to vote in each region). Since this is a single continuous variable, a number of different plots are available to use. We will use a histogram.

The different components required for a ggplot are:-

ggplot(cleaned, aes(x = Electorate)) + geom_histogram()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Changing the type of plot, for the same variable, can be achieved by changing the geom_

ggplot(cleaned, aes(x = Electorate)) + geom_density()

ggplot(cleaned, aes(x = Electorate)) +geom_histogram(aes(y = ..density..)) + geom_density(col="blue")
Warning: The dot-dot notation (`..density..`) was deprecated in ggplot2 3.4.0.
ℹ Please use `after_stat(density)` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning
was generated.
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

N.B the gghistogram function from ggpubr combined with stat_overlay_normal_density gives us a way to overlay the standard normal distribution

gghistogram(cleaned, "Electorate",y = "..density..") + stat_overlay_normal_density(col="red",lty=2)
Warning: Using `bins = 30` by default. Pick better value with the argument `bins`.


Exercise


Lets see if the number of votes is related to the size of the electorate (it should be!).

ggplot(cleaned, aes(x = Electorate, y = `Total votes`)) +
  geom_point()

Since we can add different types of plot, it will be useful to add a straight line.

ggplot(cleaned, aes(x = Electorate, y = `Total votes`)) +
  geom_point() + geom_smooth(method = "lm")
`geom_smooth()` using formula = 'y ~ x'

The add-on package ggpubr allows us to add the correlation value onto the graph. Other functions from this package allow us to add p-values etc when comparing distributions via boxplots.

library(ggpubr)
ggplot(cleaned, aes(x = Electorate, y = `Total votes`)) +
  geom_point() + geom_smooth(method = "lm") + stat_cor()
`geom_smooth()` using formula = 'y ~ x'

Is this same trend observed for all Countries? One way to look at this would be use different colours. This can be done by defining the col aesthetic. It will assign colours in a sensible fashion, and we can also define our own palette.

ggplot(cleaned, aes(x = Electorate, y = `Total votes`, col = Country)) +
  geom_point()

Colours can be defined using the scale_color_manual function. Here we make use of the RColorBrewer package for a pre-defined set. However, in the following code the contents of my_colours could be any combination of colour names or rgb.

library(RColorBrewer)
my_colours <- brewer.pal(4, "Set1")
my_colours
[1] "#E41A1C" "#377EB8" "#4DAF4A" "#984EA3"
ggplot(cleaned, aes(x = Electorate, y = `Total votes`, col = Country)) +
  geom_point() + scale_color_manual(values=my_colours)

A useful plotting technique is to split a figure depending on a variable. Here, we can plot the size of electorate against total votes for each country

ggplot(cleaned, aes(x = Electorate, y = `Total votes`)) +
  geom_point() + geom_smooth(method = "lm") + stat_cor() + facet_wrap(~Country)
`geom_smooth()` using formula = 'y ~ x'

We can compare numbers of votes between different regions. Let’s look at Sheffield

shef_data <- filter(cleaned, grepl("SHEFF",Constituency))
ggplot(shef_data, aes(x = Constituency, y = Conservative)) + geom_col(fill="#0087DC")

But how many votes did the parties get overall, and who won? We can start by adding up the respective columns. The summarize/summarise collection of functions allow many ways to summarise our data. The general approach is to use specify what summary function you want to use on which columns, and say what you want the result to be called:-

## na.rm needed as there are some 'NA' values which would result in 'NA' for the sum

summarise(cleaned, 
          Conservative_Votes = sum(Conservative,na.rm = TRUE))

Typically the summary functions are used for descriptive statistics; )mean, median, sd, var etc) and give a single value. However, we don’t want to spend our time typing out code to summarise all the colums we are interested in

## Don't do this - unless you have very few columns

summarise(cleaned, 
          Conservative_Votes = sum(Conservative,na.rm = TRUE),
          Labour_Votes = sum(Labour, na.rm = TRUE),
          Lib_Dem_Votes = sum(`Lib. Dem.`,na.rm=TRUE)
          )

dplyr is replete with shortcuts that are designed to make your life easier.

summarize_at(cleaned, vars(Conservative:Alliance),sum, na.rm = TRUE)

But what happens if we try and visualise? Try and write the ggplot code.

## don't try and run this - it won't work!
cleaned %>% 
  summarize_at(vars(Conservative:Alliance),sum, na.rm = TRUE) %>% 
  ggplot(aes(x = , y = )) + geom_col()

We can’t do this at the moment, because the data are “wide” rather than “long”. To make the data long, we can specify a set of paired key:value columns. In our case, we want a column that gives a number of votes, and a column to tell us which party those votes were for.

vote_sums <- summarize_at(cleaned, vars(Conservative:Alliance),sum, na.rm = TRUE)
pivot_longer(vote_sums, everything(), values_to = "Votes", names_to = "Party")

Let’s talk about “piping”

A relatively recent addition to the R language is the concept of piping, which can help us to make code easier to write (and read). The premise is that one line of code is used as the input for the following line. This is especially effective in dplyr, where must functions take a data frame as an input and return a data frame as an output. When looking at code online, you will see many examples of code written in this fashion. The %>% symbols at the end of a line are a giveaway. The shortcut CTRL + SHIFT + M is used to print this.

total_votes <- 
cleaned %>% 
  summarize_at(vars(Conservative:Other),sum, na.rm = TRUE) %>% 
  pivot_longer(everything(),values_to = "Votes", names_to = "Party") %>% 
  mutate(Party = fct_reorder(Party, Votes))

You can also “pipe” between dplyr and ggplot2 code

total_votes %>% 
  ggplot(aes(x = Party, y = Votes, fill=Party)) + geom_col()

However, these summed values don’t acutally tell us who one the election. Instead, we need to know who won in each constituency.

cleaned %>% 
  pivot_longer(Conservative:Other, names_to = "Party", values_to ="Votes") %>% 
  filter(!is.na(Votes))
NA

Calculate the percentage of votes for each party in each constituency.

cleaned %>% 
  pivot_longer(Conservative:Other, names_to = "Party", values_to ="Votes") %>% 
  filter(!is.na(Votes)) %>% 
  mutate(`Percentage of Vote` = (Votes / `Total votes`)*100)

We can now drill-down into the results for particular constituencies. Lets’ look at all the Sheffield results for the four biggest parties.

shef_votes <- cleaned %>% 
  pivot_longer(Conservative:Other, names_to = "Party", values_to ="Votes") %>% 
  filter(!is.na(Votes)) %>% 
  mutate(`Percentage of Vote` = (Votes / `Total votes`)*100) %>% 
  filter(grepl("SHEFFIELD", Constituency), 
         Party %in% c("Conservative", "Green", "Labour", "Lib. Dem.")) 

Exercise

Produce the following graph to show the vote split for the different parts of Sheffield. These colours were used in the example plot:-

c("#0087DC", "#008066","#DC241f","#FDBB30")

Now to determine the winners for each constituency. To do this, we will order the percentage of votes within each constituency by first grouping within constituency and then using arrange.

cleaned %>% 
  pivot_longer(Conservative:Other, names_to = "Party", values_to ="Votes") %>% 
  filter(!is.na(Votes)) %>% 
  mutate(`Percentage of Vote` = (Votes / `Total votes`)*100) %>% 
  group_by(Constituency) %>% 
  arrange(desc(`Percentage of Vote`),.by_group = TRUE)

To calculate the majority (i.e. how much each seat was won by), we’ll need the first and second place votes. The lead function is used to find the second highest number of votes.

cleaned %>% 
  pivot_longer(Conservative:Other, names_to = "Party", values_to ="Votes") %>% 
  filter(!is.na(Votes)) %>% 
  mutate(`Percentage of Vote` = (Votes / `Total votes`)*100) %>% 
  group_by(Constituency) %>% 
  arrange(desc(`Percentage of Vote`),.by_group = TRUE) %>% 
  slice(1:2) %>% 
  mutate(`Second Place` = lead(`Percentage of Vote`,order_by = Constituency), Majority = `Percentage of Vote` - `Second Place`)

Finally, we can select the winner of each seat and their majority.

results <- cleaned %>% 
  pivot_longer(Conservative:Other, names_to = "Party", values_to ="Votes") %>% 
  filter(!is.na(Votes)) %>% 
  mutate(`Percentage of Vote` = (Votes / `Total votes`)*100) %>% 
  group_by(Constituency) %>% 
  arrange(desc(`Percentage of Vote`),.by_group = TRUE) %>% 
  slice(1:2) %>% 
  mutate(`Second Place` = lead(`Percentage of Vote`,order_by = Constituency), Majority = `Percentage of Vote` - `Second Place`) %>% 
  slice(1)

Did some parties win by slimmer majorities?

results %>% 
  filter(Party %in% c("Conservative", "Green", "Labour", "Lib. Dem.","SNP")) %>% 
  ggplot(aes(x = Party, y = Majority,fill=Party)) + geom_boxplot() + geom_jitter(width=0.1) + scale_fill_manual(values=c("#0087DC", "#008066","#DC241f","#FDBB30","#FFFF00"))

We can now work out the number of seats

seats_2019 %>% 
  filter(Party %in% c("Conservative", "Green", "Labour", "Lib. Dem.","SNP")) %>% 
ggplot(aes(x = fct_reorder(Party, Seats),y = Seats,fill=Party)) + geom_col() + scale_fill_manual(values=c("#0087DC", "#008066","#DC241f","#FDBB30","#FFFF00"))

LS0tCnRpdGxlOiAiUmV2aWV3IG9mIERhdGEgTWFuaXB1bGF0aW9uIGFuZCBWaXN1YWxpemF0aW9uIHVzaW5nIHRpZHl2ZXJzZSIKYXV0aG9yOiBNYXJrIER1bm5pbmcgLSBTaGVmZmllbGQgQmlvaW5mb3JtYXRpY3MgQ29yZQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKZGF0ZTogIkxhc3QgdXBkYXRlZCBvbiBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCLCAlWScpYCIKLS0tCgohW10obG9nby1zbS5wbmcpCgotIFtTaGVmZmllbGQgQmlvaW5mb3JtYXRpY3MgQ29yZV0oaHR0cHM6Ly9zYmMuc2hlZi5hYy51aykKLSBFbWFpbDogYmlvaW5mb3JtYXRpY3MtY29yZUBzaGVmZmllbGQuYWMudWsKCiMgUmVxdWlyZWQgcGFja2FnZXMKCmBgYHtyIGV2YWw9RkFMU0V9Cmluc3RhbGwucGFja2FnZXMoYygidGlkeXZlcnNlIiwgImdncHViciIsICJyZWFkeGwiKSkKYGBgCgojIERhdHNldHMgCgpXZSB3aWxsIGJlIHVzaW5nIGhpc3RvcmljYWwgZGF0YSBvbiBVayBlbGVjdGlvbiByZXN1bHRzLiBJZiB0aW1lIGFsbG93cywgd2Ugd2lsbCBleHBsb3JlIHRoZSAyMDI0IHJlc3VsdHMKCi0gW0hpc3RvcmljYWwgRWxlY3Rpb24gUmVzdWx0c10oMTkxOC0yMDE5ZWxlY3Rpb25fcmVzdWx0c19ieV9wY29uLnhsc3gpCi0gWzIwMTkgRWxlY3Rpb24gUmVzdWx0c10odWtfZWxlY3Rpb25fMjAxOS5jc3YpCi0gWzIwMjQgRWxlY3Rpb24gUmVzdWx0c10oaHR0cHM6Ly9yZXNlYXJjaGJyaWVmaW5ncy5maWxlcy5wYXJsaWFtZW50LnVrL2RvY3VtZW50cy9DQlAtMTAwMDkvSG9DLUdFMjAyNC1yZXN1bHRzLWJ5LWNvbnN0aXR1ZW5jeS5jc3YpCgoKIyBPdXRsaW5lCgpJbiB0aGlzIHR1dG9yaWFsIHdlIHdpbGwgaW50cm9kdWN0aW9uIC8gcmV2aWV3IHNvbWUgY29uY2VwdHMgb2YgdXNpbmcgYGRwbHlyYCwgYGdncGxvdDJgIGFuZCBvdGhlciBwYXJ0cyBvZiB0aGUgInRpZHl2ZXJzZSIuIEJyaWVmbHk6LQoKLSBEYXRhIEltcG9ydAogIC0gRnJvbSBYbHN4IGFuZCBjc3YgZm9ybWF0cwotIERhdGEgQ2xlYW5pbmcKICAtIFJlbmFtaW5nIGNvbHVtbnMsIGZpbHRlcmluZywgdHJhbnNmb3JtaW5nIGNvbHVtbnMKLSBEYXRhIE1hbmlwdWxhdGlvbgogIC0gIldpZGUiIHZzICJMb25nIiBkYXRhCiAgLSAoUmUtKWFycmFuZ2luZyByb3dzCiAgLSBHcm91cGluZyBhbmQgc3VtbWFyaXNpbmcgdGFibGVzCi0gRGF0YSBWaXN1YWxpc2F0aW9uCiAgLSBCYXNpYyBwbG90IHR5cGVzCiAgLSBTcGxpdHRpbmctdXAgcGxvdHMgYmFzZWQgb24gdmFyaWFibGVzIC0gZmFjZXRpbmcKICAtIFVzaW5nIGNvbG91cnMKCgpGb3IgbW9yZSBpbmZvcm1hdGlvbiBvbiB0aGVzZSBrZXkgcGFja2FnZXMgeW91IGNhbiBjb25zdWx0IHRoZSAiY2hlYXRzaGVldHMiIGF2YWlsYWJsZSB0aHJvdWdoIHRoZSBoZWxwIG1lbnVzLCBvciBmcm9tIHRoZSBbdGlkeXZlcnNlXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLykgd2Vic2l0ZSBpdHNlbGYuCgojIEdldCBTdGFydGVkCgpMZXRzJyBsb2FkIHRoZSBwYWNrYWdlcyB3ZSB3aWxsIG5lZWQuIGB0aWR5dmVyc2VgIHdpbGwgbG9hZCBhbGwgdGhlIHBhY2thZ2VzIHdlIG5lZWQKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShyZWFkeGwpCmBgYAoKCkZpcnN0IGF0dGVtcHQgdG8gcmVhZCB1c2luZyB0aGUgYHJlYWRfeGxzeGAgcGFja2FnZSAtIHdoaWNoIGlzIGRldmVsb3BlZCBzcGVjaWZpY2FsbHkgZm9yIGB4bHNgIGFuZCBgeGxzeGAgZmlsZXMuIEV2ZW4gdGhvdWdoIHRoZSBmdW5jdGlvbiBydW5zIHdpdGhvdXQgZXJyb3IsIGl0J3MgaGlnaGx5IHJlY29tbWVuZGVkIHRvIGBWaWV3YCB0aGUgY29udGVudHMgb2YgdGhlIGRhdGEgZnJhbWUuCgpgYGB7ciBtZXNzYWdlPUZBTFNFfQpyYXcgPC0gcmVhZF94bHN4KCIxOTE4LTIwMTllbGVjdGlvbl9yZXN1bHRzX2J5X3Bjb24ueGxzeCIpClZpZXcocmF3KQpgYGAKClRoZXJlJ3MgY2xlYXJseSBzb21lIGhlYWRlciBpbmZvcm1hdGlvbiB3ZSBuZWVkIHRvIGRlYWwgd2l0aC4gVGhlIGBza2lwYCBhcmd1bWVudCBhbGxvd3Mgc29tZSByb3dzIGluIHRoZSBpbnB1dCBmaWxlIHRvIGJlIGlnbm9yZWQuIAoKYGBge3IgbWVzc2FnZT1GQUxTRX0KcmF3IDwtIHJlYWRfeGxzeCgiMTkxOC0yMDE5ZWxlY3Rpb25fcmVzdWx0c19ieV9wY29uLnhsc3giLCBza2lwID0gMykKcmF3CmBgYAoKQW5kIHdlIG5lZWQgdG8gc2VsZWN0IHRoZSBwYXJ0aWN1bGFyIHNoZWV0IHRoYXQgd2UncmUgaW50ZXJlc3RlZCBpbiAKCmBgYHtyfQpleGNlbF9zaGVldHMoIjE5MTgtMjAxOWVsZWN0aW9uX3Jlc3VsdHNfYnlfcGNvbi54bHN4IikKcmF3IDwtIHJlYWRfeGxzeCgiMTkxOC0yMDE5ZWxlY3Rpb25fcmVzdWx0c19ieV9wY29uLnhsc3giLCBza2lwID0gMyxzaGVldCA9IDI5KQpgYGAKCkxldHMgdXNlIGRwbHlyIHRvIHBpY2sgY29sdW1ucyBvZiBpbnRlcmVzdC4gU29tZSBtYW51YWwgcmVuYW1pbmcgaW52b2x2ZWQgOi8KCmBgYHtyfQpjbGVhbmVkIDwtIHJhdyAlPiUgCiAgc2VsZWN0KENvbnN0aXR1ZW5jeTpFbGVjdG9yYXRlLCBUdXJub3V0LCBgVG90YWwgdm90ZXNgLCBjb250YWlucygiVm90ZXMuLi4iKSkgJT4lIAogIHJlbmFtZSgiQ29uc2VydmF0aXZlIiA9IGBWb3Rlcy4uLjhgLAogICAgICAgICAiTGFib3VyIiA9IGBWb3Rlcy4uLjExYCwKICAgICAgICAgIkxpYi4gRGVtLiIgPSBgVm90ZXMuLi4xNGAsCiAgICAgICAgICJCcmV4aXQiID0gYFZvdGVzLi4uMTdgLAogICAgICAgICAiR3JlZW4iID0gYFZvdGVzLi4uMjBgLAogICAgICAgICAiU05QIiA9IGBWb3Rlcy4uLjIzYCwKICAgICAgICAgIlBsYWlkIEN5bXJ1IiA9IGBWb3Rlcy4uLjI2YCwKICAgICAgICAgIkRVUCIgPSBgVm90ZXMuLi4yOWAsCiAgICAgICAgICJTaW5uIEZlaW4iID0gYFZvdGVzLi4uMzJgLAogICAgICAgICAiU0RMUCIgPSBgVm90ZXMuLi4zNWAsCiAgICAgICAgICJVVVAiID0gYFZvdGVzLi4uMzhgLAogICAgICAgICAiQWxsaWFuY2UiID0gYFZvdGVzLi4uNDFgLAogICAgICAgICAiT3RoZXIiID0gYFZvdGVzLi4uNDRgKQpjbGVhbmVkCmBgYApIb3dldmVyLCBhIGJpdCBvZiBpbnNwZWN0aW9uIHNob3dzIHNvbWUgZXh0cmEgcm93cyBpbiB0aGUgZmlsZSB0aGF0IHNob3VsZG4ndCBoYXZlIGJlZW4gaW5jbHVkZWQuCgpgYGB7cn0KdGFpbChjbGVhbmVkKQpgYGAKClN1Y2ggcHJvYmxlbWF0aWMgcm93cyBjYW4gYmUgaWRlbnRpZmllZCBieSBoYXZpbmcgYW4gYE5BYCBpbiB0aGUgYENvdW50cnlgIGNvbHVtbgoKYGBge3J9CmNsZWFuZWQgPC0gZmlsdGVyKGNsZWFuZWQsICFpcy5uYShDb3VudHJ5KSkKYGBgCgoKV2UgY2FuIHNhdmUgYXMgY3N2IGZpbGUgZm9yIGZ1dHVyZSB1c2UuCgpgYGB7cn0Kd3JpdGVfY3N2KGNsZWFuZWQsICJ1a19lbGVjdGlvbl8yMDE5LmNzdiIpCmBgYAoKSWYgYXQgYW55IHBvaW50IHdlIG5lZWQgdG8gcmVhZCB0aGUgb3JpZ2luYWwgZGF0YSB3ZSBjYW4gZG86LQoKYGBge3J9CmNsZWFuZWQgPC0gcmVhZF9jc3YoInVrX2VsZWN0aW9uXzIwMTkuY3N2IikKYGBgCgpBcyBwYXJ0IG9mIHVuZGVyc3RhbmRpbmcgdGhlIGRhdGEgaXQgaXMgaGVscGZ1bCB0byBjYXJyeSBvdXQgYSBmZXcgY2hlY2tzIG9uIHRoZSBkYXRhIHF1YWxpdHkuIEFsdGhvdWdoIHdlIGhhdmUgdGhlIHRvdGFsIG51bWJlciBvZiB2b3RlcyBhcyBhIGNvbHVtbiwgd2UgY2FuIGNoZWNrIHRoYXQgdGhpcyBoYXMgYmVlbiBjb21wdXRlZCBjb3JyZWN0bHkuIAoKYGBge3J9CmNsZWFuZWQgJT4lIAogIG11dGF0ZShUb3RhbF92b3Rlc19jYWxjID0gRWxlY3RvcmF0ZSAqIFR1cm5vdXQpICU+JQogIHNlbGVjdChgVG90YWwgdm90ZXNgLCBUb3RhbF92b3Rlc19jYWxjKQpgYGAKCldlIG5vdyBoYXZlIHNvbWUgZGF0YSB0aGF0IHdlIGNhbiBzdGFydCB0byB2aXN1YWxpc2UuIEZvciB0aGlzIHdlIGFyZSBnb2luZyB0byB1c2UgdGhlIGBnZ3Bsb3QyYCBwYWNrYWdlLiBUaGlzIGlzIGEgKmZsZXhpYmxlKiBhbmQgKmV4dGVuZGFibGUqIHBsb3R0aW5nIGZyYW1ld29yayB3aXRoIGEgY29uc2lzdGVudCBpbnRlcmZhY2UuIEl0IGNhbiBtYWtlIHB1YmxpY2F0aW9uLXJlYWR5IGZpZ3VyZXMgd2l0aCBsZXNzIGVmZm9ydCB0aGFuICJiYXNlIiBSLiBXaXRoIHNvbWUgcHJhY3RpY2UsIHlvdSB3aWxsIHNwZW5kIGxlc3MgdGltZSB3cml0aW5nIGNvZGUgdG8gbWFrZSBmaWd1cmVzIGFuZCAqKm1vcmUgdGltZSBpbnRlcnByZXRpbmcgYW5kIGludGVycm9nYXRpbmcgeW91ciBkYXRhKiogLSB3aGljaCBpcyB3aGF0IHdlIHJlYWxseSB3YW50IHRvIHNwZW5kIG91ciB0aW1lIGRvaW5nIQoKQSB1c2VmdWwgcmVzb3VyY2UgdG8gZ3VpZGUgdXMgaW4gbWFraW5nIG91ciBmaWd1cmVzIGlzIGRhdGEtdG8tdml6LiBTb21ldGltZXMgaXQgY2FuIGV2ZW4gaGVscCB0byBza2V0Y2ggYSBmaWd1cmUgZmlyc3QgdXNpbmcgcGVuY2lsIGFuZCBwYXBlci4KCi0gW0Nob29zZSB0aGUgbW9zdCBhcHByb3ByaWF0ZSBmaWd1cmUgZm9yIHlvdXIgZGF0YSAtIGRhdGEtdG8tdml6XShodHRwczovL3d3dy5kYXRhLXRvLXZpei5jb20vKQoKV2Ugc3RhcnQgYnkgbG9va2luZyBhdCB0aGUgc2l6ZSBvZiBlbGVjdG9yYXRlIHNpemUgKHRoZSBudW1iZXIgb2YgcGVvcGxlIGVsaWdpYmxlIHRvIHZvdGUgaW4gZWFjaCByZWdpb24pLiBTaW5jZSB0aGlzIGlzIGEgc2luZ2xlIGNvbnRpbnVvdXMgdmFyaWFibGUsIGEgbnVtYmVyIG9mIGRpZmZlcmVudCBwbG90cyBhcmUgYXZhaWxhYmxlIHRvIHVzZS4gV2Ugd2lsbCB1c2UgYSBoaXN0b2dyYW0uCgpUaGUgZGlmZmVyZW50IGNvbXBvbmVudHMgcmVxdWlyZWQgZm9yIGEgZ2dwbG90IGFyZTotCgotIHRoZSBkYXRhIGZyYW1lIHRoYXQgeW91IHdhbnQgdG8gcGxvdAotIGRlZmluaXRpb25zIG9mIGhvdyB0byBwbG90IHZhcmlhYmxlcyAvIGNvbHVtbnMgaW4gdGhhdCBkYXRhIHRvIHRoZSBwbG90IGFlc3RoZXRpY3MgKHgtYXhpcywgeS1heGlzLCBjb2xvdXIsIHNoYXBlIGV0YykKLSB3aGF0IHR5cGUgb2YgcGxvdCAoZ2VvbV8pIHlvdSB3YW50CgoKCmBgYHtyfQpnZ3Bsb3QoY2xlYW5lZCwgYWVzKHggPSBFbGVjdG9yYXRlKSkgKyBnZW9tX2hpc3RvZ3JhbSgpCmBgYAoKQ2hhbmdpbmcgdGhlIHR5cGUgb2YgcGxvdCwgZm9yIHRoZSBzYW1lIHZhcmlhYmxlLCBjYW4gYmUgYWNoaWV2ZWQgYnkgY2hhbmdpbmcgdGhlIGBnZW9tX2AKCmBgYHtyfQpnZ3Bsb3QoY2xlYW5lZCwgYWVzKHggPSBFbGVjdG9yYXRlKSkgKyBnZW9tX2RlbnNpdHkoKQpgYGAKYGBge3J9CmdncGxvdChjbGVhbmVkLCBhZXMoeCA9IEVsZWN0b3JhdGUpKSArZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLikpICsgZ2VvbV9kZW5zaXR5KGNvbD0iYmx1ZSIpCmBgYAoKTi5CIHRoZSBgZ2doaXN0b2dyYW1gIGZ1bmN0aW9uIGZyb20gYGdncHVicmAgY29tYmluZWQgd2l0aCBgc3RhdF9vdmVybGF5X25vcm1hbF9kZW5zaXR5YCBnaXZlcyB1cyBhIHdheSB0byBvdmVybGF5IHRoZSAqc3RhbmRhcmQqIG5vcm1hbCBkaXN0cmlidXRpb24KCmBgYHtyfQpnZ2hpc3RvZ3JhbShjbGVhbmVkLCAiRWxlY3RvcmF0ZSIseSA9ICIuLmRlbnNpdHkuLiIpICsgc3RhdF9vdmVybGF5X25vcm1hbF9kZW5zaXR5KGNvbD0icmVkIixsdHk9MikKYGBgCgoKLS0tCgoqKkV4ZXJjaXNlKioKCi0gVmlzdWFsaXNlIHRoZSBkaXN0cmlidXRpb24gb2YgZWxlY3RvcmF0ZSBzaXplcyBpbiBkaWZmZXJlbnQgY291bnRyaWVzPwogIC0gYXMgYSBib3hwbG90IHdpdGggaml0dGVyZWQgKGdlb21faml0dGVyKSBwb2ludHMgb3ZlcmxhaWQKICAKIVtdKGVsZWN0b3JhdGUtc2l6ZS1jb21wYXJpc29uLnBuZykgIAoKLS0tCgpMZXRzIHNlZSBpZiB0aGUgbnVtYmVyIG9mIHZvdGVzIGlzIHJlbGF0ZWQgdG8gdGhlIHNpemUgb2YgdGhlIGVsZWN0b3JhdGUgKGl0IHNob3VsZCBiZSEpLiAKCgpgYGB7cn0KZ2dwbG90KGNsZWFuZWQsIGFlcyh4ID0gRWxlY3RvcmF0ZSwgeSA9IGBUb3RhbCB2b3Rlc2ApKSArCiAgZ2VvbV9wb2ludCgpCmBgYApTaW5jZSB3ZSBjYW4gYWRkIGRpZmZlcmVudCB0eXBlcyBvZiBwbG90LCBpdCB3aWxsIGJlIHVzZWZ1bCB0byBhZGQgYSBzdHJhaWdodCBsaW5lLgoKYGBge3J9CmdncGxvdChjbGVhbmVkLCBhZXMoeCA9IEVsZWN0b3JhdGUsIHkgPSBgVG90YWwgdm90ZXNgKSkgKwogIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpCmBgYAoKVGhlIGFkZC1vbiBwYWNrYWdlIGBnZ3B1YnJgIGFsbG93cyB1cyB0byBhZGQgdGhlIGNvcnJlbGF0aW9uIHZhbHVlIG9udG8gdGhlIGdyYXBoLiBPdGhlciBmdW5jdGlvbnMgZnJvbSB0aGlzIHBhY2thZ2UgYWxsb3cgdXMgdG8gYWRkIHAtdmFsdWVzIGV0YyB3aGVuIGNvbXBhcmluZyBkaXN0cmlidXRpb25zIHZpYSBib3hwbG90cy4KCmBgYHtyfQpsaWJyYXJ5KGdncHVicikKZ2dwbG90KGNsZWFuZWQsIGFlcyh4ID0gRWxlY3RvcmF0ZSwgeSA9IGBUb3RhbCB2b3Rlc2ApKSArCiAgZ2VvbV9wb2ludCgpICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikgKyBzdGF0X2NvcigpCmBgYAoKSXMgdGhpcyBzYW1lIHRyZW5kIG9ic2VydmVkIGZvciBhbGwgQ291bnRyaWVzPyBPbmUgd2F5IHRvIGxvb2sgYXQgdGhpcyB3b3VsZCBiZSB1c2UgZGlmZmVyZW50IGNvbG91cnMuIFRoaXMgY2FuIGJlIGRvbmUgYnkgZGVmaW5pbmcgdGhlIGBjb2xgIGFlc3RoZXRpYy4gSXQgd2lsbCBhc3NpZ24gY29sb3VycyBpbiBhIHNlbnNpYmxlIGZhc2hpb24sIGFuZCB3ZSBjYW4gYWxzbyBkZWZpbmUgb3VyIG93biBwYWxldHRlLgoKCgpgYGB7cn0KZ2dwbG90KGNsZWFuZWQsIGFlcyh4ID0gRWxlY3RvcmF0ZSwgeSA9IGBUb3RhbCB2b3Rlc2AsIGNvbCA9IENvdW50cnkpKSArCiAgZ2VvbV9wb2ludCgpCmBgYApDb2xvdXJzIGNhbiBiZSBkZWZpbmVkIHVzaW5nIHRoZSBgc2NhbGVfY29sb3JfbWFudWFsYCBmdW5jdGlvbi4gSGVyZSB3ZSBtYWtlIHVzZSBvZiB0aGUgYFJDb2xvckJyZXdlcmAgcGFja2FnZSBmb3IgYSBwcmUtZGVmaW5lZCBzZXQuIEhvd2V2ZXIsIGluIHRoZSBmb2xsb3dpbmcgY29kZSB0aGUgY29udGVudHMgb2YgYG15X2NvbG91cnNgIGNvdWxkIGJlIGFueSBjb21iaW5hdGlvbiBvZiBjb2xvdXIgbmFtZXMgb3IgcmdiLgoKYGBge3J9CmxpYnJhcnkoUkNvbG9yQnJld2VyKQpteV9jb2xvdXJzIDwtIGJyZXdlci5wYWwoNCwgIlNldDEiKQpteV9jb2xvdXJzCmdncGxvdChjbGVhbmVkLCBhZXMoeCA9IEVsZWN0b3JhdGUsIHkgPSBgVG90YWwgdm90ZXNgLCBjb2wgPSBDb3VudHJ5KSkgKwogIGdlb21fcG9pbnQoKSArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9bXlfY29sb3VycykKYGBgCgoKQSB1c2VmdWwgcGxvdHRpbmcgdGVjaG5pcXVlIGlzIHRvIHNwbGl0IGEgZmlndXJlIGRlcGVuZGluZyBvbiBhIHZhcmlhYmxlLiBIZXJlLCB3ZSBjYW4gcGxvdCB0aGUgc2l6ZSBvZiBlbGVjdG9yYXRlIGFnYWluc3QgdG90YWwgdm90ZXMgZm9yIGVhY2ggY291bnRyeQoKYGBge3J9CmdncGxvdChjbGVhbmVkLCBhZXMoeCA9IEVsZWN0b3JhdGUsIHkgPSBgVG90YWwgdm90ZXNgKSkgKwogIGdlb21fcG9pbnQoKSArIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpICsgc3RhdF9jb3IoKSArIGZhY2V0X3dyYXAofkNvdW50cnkpCmBgYAoKV2UgY2FuIGNvbXBhcmUgbnVtYmVycyBvZiB2b3RlcyBiZXR3ZWVuIGRpZmZlcmVudCByZWdpb25zLiBMZXQncyBsb29rIGF0IFNoZWZmaWVsZAoKYGBge3J9CnNoZWZfZGF0YSA8LSBmaWx0ZXIoY2xlYW5lZCwgZ3JlcGwoIlNIRUZGIixDb25zdGl0dWVuY3kpKQpgYGAKYGBge3J9CmdncGxvdChzaGVmX2RhdGEsIGFlcyh4ID0gQ29uc3RpdHVlbmN5LCB5ID0gQ29uc2VydmF0aXZlKSkgKyBnZW9tX2NvbChmaWxsPSIjMDA4N0RDIikKYGBgCgpCdXQgaG93IG1hbnkgdm90ZXMgZGlkIHRoZSBwYXJ0aWVzIGdldCBvdmVyYWxsLCBhbmQgd2hvIHdvbj8gV2UgY2FuIHN0YXJ0IGJ5IGFkZGluZyB1cCB0aGUgcmVzcGVjdGl2ZSBjb2x1bW5zLiBUaGUgYHN1bW1hcml6ZWAvYHN1bW1hcmlzZWAgY29sbGVjdGlvbiBvZiBmdW5jdGlvbnMgYWxsb3cgbWFueSB3YXlzIHRvIHN1bW1hcmlzZSBvdXIgZGF0YS4gVGhlIGdlbmVyYWwgYXBwcm9hY2ggaXMgdG8gdXNlIHNwZWNpZnkgd2hhdCBzdW1tYXJ5IGZ1bmN0aW9uIHlvdSB3YW50IHRvIHVzZSBvbiB3aGljaCBjb2x1bW5zLCBhbmQgc2F5IHdoYXQgeW91IHdhbnQgdGhlIHJlc3VsdCB0byBiZSBjYWxsZWQ6LQoKYGBge3J9CiMjIG5hLnJtIG5lZWRlZCBhcyB0aGVyZSBhcmUgc29tZSAnTkEnIHZhbHVlcyB3aGljaCB3b3VsZCByZXN1bHQgaW4gJ05BJyBmb3IgdGhlIHN1bQoKc3VtbWFyaXNlKGNsZWFuZWQsIAogICAgICAgICAgQ29uc2VydmF0aXZlX1ZvdGVzID0gc3VtKENvbnNlcnZhdGl2ZSxuYS5ybSA9IFRSVUUpKQpgYGAKVHlwaWNhbGx5IHRoZSBzdW1tYXJ5IGZ1bmN0aW9ucyBhcmUgdXNlZCBmb3IgZGVzY3JpcHRpdmUgc3RhdGlzdGljczsgKWBtZWFuYCwgYG1lZGlhbmAsIGBzZGAsIGB2YXJgIGV0YykgYW5kIGdpdmUgYSAqKnNpbmdsZSB2YWx1ZSoqLiBIb3dldmVyLCB3ZSBkb24ndCB3YW50IHRvIHNwZW5kIG91ciB0aW1lIHR5cGluZyBvdXQgY29kZSB0byBzdW1tYXJpc2UgYWxsIHRoZSBjb2x1bXMgd2UgYXJlIGludGVyZXN0ZWQgaW4KCmBgYHtyfQojIyBEb24ndCBkbyB0aGlzIC0gdW5sZXNzIHlvdSBoYXZlIHZlcnkgZmV3IGNvbHVtbnMKCnN1bW1hcmlzZShjbGVhbmVkLCAKICAgICAgICAgIENvbnNlcnZhdGl2ZV9Wb3RlcyA9IHN1bShDb25zZXJ2YXRpdmUsbmEucm0gPSBUUlVFKSwKICAgICAgICAgIExhYm91cl9Wb3RlcyA9IHN1bShMYWJvdXIsIG5hLnJtID0gVFJVRSksCiAgICAgICAgICBMaWJfRGVtX1ZvdGVzID0gc3VtKGBMaWIuIERlbS5gLG5hLnJtPVRSVUUpCiAgICAgICAgICApCmBgYApgZHBseXJgIGlzIHJlcGxldGUgd2l0aCBzaG9ydGN1dHMgdGhhdCBhcmUgZGVzaWduZWQgdG8gbWFrZSB5b3VyIGxpZmUgZWFzaWVyLiAKCmBgYHtyfQpzdW1tYXJpemVfYXQoY2xlYW5lZCwgdmFycyhDb25zZXJ2YXRpdmU6QWxsaWFuY2UpLHN1bSwgbmEucm0gPSBUUlVFKQpgYGAKQnV0IHdoYXQgaGFwcGVucyBpZiB3ZSB0cnkgYW5kIHZpc3VhbGlzZT8gVHJ5IGFuZCB3cml0ZSB0aGUgYGdncGxvdGAgY29kZS4KCmBgYHtyIGV2YWw9RkFMU0V9CiMjIGRvbid0IHRyeSBhbmQgcnVuIHRoaXMgLSBpdCB3b24ndCB3b3JrIQpjbGVhbmVkICU+JSAKICBzdW1tYXJpemVfYXQodmFycyhDb25zZXJ2YXRpdmU6QWxsaWFuY2UpLHN1bSwgbmEucm0gPSBUUlVFKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gLCB5ID0gKSkgKyBnZW9tX2NvbCgpCmBgYAoKV2UgY2FuJ3QgZG8gdGhpcyBhdCB0aGUgbW9tZW50LCBiZWNhdXNlIHRoZSBkYXRhIGFyZSAid2lkZSIgcmF0aGVyIHRoYW4gImxvbmciLiBUbyBtYWtlIHRoZSBkYXRhIGxvbmcsIHdlIGNhbiBzcGVjaWZ5IGEgc2V0IG9mIHBhaXJlZCBrZXk6dmFsdWUgY29sdW1ucy4gSW4gb3VyIGNhc2UsIHdlIHdhbnQgYSBjb2x1bW4gdGhhdCBnaXZlcyBhIG51bWJlciBvZiB2b3RlcywgYW5kIGEgY29sdW1uIHRvIHRlbGwgdXMgd2hpY2ggcGFydHkgdGhvc2Ugdm90ZXMgd2VyZSBmb3IuCgoKYGBge3J9CnZvdGVfc3VtcyA8LSBzdW1tYXJpemVfYXQoY2xlYW5lZCwgdmFycyhDb25zZXJ2YXRpdmU6QWxsaWFuY2UpLHN1bSwgbmEucm0gPSBUUlVFKQpwaXZvdF9sb25nZXIodm90ZV9zdW1zLCBldmVyeXRoaW5nKCksIHZhbHVlc190byA9ICJWb3RlcyIsIG5hbWVzX3RvID0gIlBhcnR5IikKYGBgCgojIyBMZXQncyB0YWxrIGFib3V0ICJwaXBpbmciCgpBIHJlbGF0aXZlbHkgcmVjZW50IGFkZGl0aW9uIHRvIHRoZSBSIGxhbmd1YWdlIGlzIHRoZSBjb25jZXB0IG9mIHBpcGluZywgd2hpY2ggY2FuIGhlbHAgdXMgdG8gbWFrZSBjb2RlIGVhc2llciB0byB3cml0ZSAoYW5kIHJlYWQpLiBUaGUgcHJlbWlzZSBpcyB0aGF0IG9uZSBsaW5lIG9mIGNvZGUgaXMgdXNlZCBhcyB0aGUgaW5wdXQgZm9yIHRoZSBmb2xsb3dpbmcgbGluZS4gVGhpcyBpcyBlc3BlY2lhbGx5IGVmZmVjdGl2ZSBpbiBkcGx5ciwgd2hlcmUgbXVzdCBmdW5jdGlvbnMgdGFrZSBhIGRhdGEgZnJhbWUgYXMgYW4gaW5wdXQgYW5kIHJldHVybiBhIGRhdGEgZnJhbWUgYXMgYW4gb3V0cHV0LiBXaGVuIGxvb2tpbmcgYXQgY29kZSBvbmxpbmUsIHlvdSB3aWxsIHNlZSBtYW55IGV4YW1wbGVzIG9mIGNvZGUgd3JpdHRlbiBpbiB0aGlzIGZhc2hpb24uIFRoZSBgJT4lYCBzeW1ib2xzIGF0IHRoZSBlbmQgb2YgYSBsaW5lIGFyZSBhIGdpdmVhd2F5LiBUaGUgc2hvcnRjdXQgQ1RSTCArIFNISUZUICsgTSBpcyB1c2VkIHRvIHByaW50IHRoaXMuCgpgYGB7cn0KdG90YWxfdm90ZXMgPC0gCmNsZWFuZWQgJT4lIAogIHN1bW1hcml6ZV9hdCh2YXJzKENvbnNlcnZhdGl2ZTpPdGhlciksc3VtLCBuYS5ybSA9IFRSVUUpICU+JSAKICBwaXZvdF9sb25nZXIoZXZlcnl0aGluZygpLHZhbHVlc190byA9ICJWb3RlcyIsIG5hbWVzX3RvID0gIlBhcnR5IikgJT4lIAogIG11dGF0ZShQYXJ0eSA9IGZjdF9yZW9yZGVyKFBhcnR5LCBWb3RlcykpCmBgYAoKWW91IGNhbiBhbHNvICJwaXBlIiBiZXR3ZWVuIGRwbHlyIGFuZCBnZ3Bsb3QyIGNvZGUKCmBgYHtyfQp0b3RhbF92b3RlcyAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gUGFydHksIHkgPSBWb3RlcywgZmlsbD1QYXJ0eSkpICsgZ2VvbV9jb2woKQpgYGAKSG93ZXZlciwgdGhlc2Ugc3VtbWVkIHZhbHVlcyBkb24ndCBhY3V0YWxseSB0ZWxsIHVzIHdobyBvbmUgdGhlIGVsZWN0aW9uLiBJbnN0ZWFkLCB3ZSBuZWVkIHRvIGtub3cgd2hvIHdvbiBpbiBlYWNoIGNvbnN0aXR1ZW5jeS4KCmBgYHtyfQpjbGVhbmVkICU+JSAKICBwaXZvdF9sb25nZXIoQ29uc2VydmF0aXZlOk90aGVyLCBuYW1lc190byA9ICJQYXJ0eSIsIHZhbHVlc190byA9IlZvdGVzIikgJT4lIAogIGZpbHRlcighaXMubmEoVm90ZXMpKQogIApgYGAKCkNhbGN1bGF0ZSB0aGUgcGVyY2VudGFnZSBvZiB2b3RlcyBmb3IgZWFjaCBwYXJ0eSBpbiBlYWNoIGNvbnN0aXR1ZW5jeS4KCmBgYHtyfQpjbGVhbmVkICU+JSAKICBwaXZvdF9sb25nZXIoQ29uc2VydmF0aXZlOk90aGVyLCBuYW1lc190byA9ICJQYXJ0eSIsIHZhbHVlc190byA9IlZvdGVzIikgJT4lIAogIGZpbHRlcighaXMubmEoVm90ZXMpKSAlPiUgCiAgbXV0YXRlKGBQZXJjZW50YWdlIG9mIFZvdGVgID0gKFZvdGVzIC8gYFRvdGFsIHZvdGVzYCkqMTAwKQpgYGAKV2UgY2FuIG5vdyBkcmlsbC1kb3duIGludG8gdGhlIHJlc3VsdHMgZm9yIHBhcnRpY3VsYXIgY29uc3RpdHVlbmNpZXMuIExldHMnIGxvb2sgYXQgYWxsIHRoZSBTaGVmZmllbGQgcmVzdWx0cyBmb3IgdGhlIGZvdXIgYmlnZ2VzdCBwYXJ0aWVzLiAKCmBgYHtyfQpzaGVmX3ZvdGVzIDwtIGNsZWFuZWQgJT4lIAogIHBpdm90X2xvbmdlcihDb25zZXJ2YXRpdmU6T3RoZXIsIG5hbWVzX3RvID0gIlBhcnR5IiwgdmFsdWVzX3RvID0iVm90ZXMiKSAlPiUgCiAgZmlsdGVyKCFpcy5uYShWb3RlcykpICU+JSAKICBtdXRhdGUoYFBlcmNlbnRhZ2Ugb2YgVm90ZWAgPSAoVm90ZXMgLyBgVG90YWwgdm90ZXNgKSoxMDApICU+JSAKICBmaWx0ZXIoZ3JlcGwoIlNIRUZGSUVMRCIsIENvbnN0aXR1ZW5jeSksIAogICAgICAgICBQYXJ0eSAlaW4lIGMoIkNvbnNlcnZhdGl2ZSIsICJHcmVlbiIsICJMYWJvdXIiLCAiTGliLiBEZW0uIikpIApgYGAKCi0tLS0KCioqRXhlcmNpc2UqKgoKUHJvZHVjZSB0aGUgZm9sbG93aW5nIGdyYXBoIHRvIHNob3cgdGhlIHZvdGUgc3BsaXQgZm9yIHRoZSBkaWZmZXJlbnQgcGFydHMgb2YgU2hlZmZpZWxkLiBUaGVzZSBjb2xvdXJzIHdlcmUgdXNlZCBpbiB0aGUgZXhhbXBsZSBwbG90Oi0KCmBgYHtyIGV2YWw9RkFMU0V9CmMoIiMwMDg3REMiLCAiIzAwODA2NiIsIiNEQzI0MWYiLCIjRkRCQjMwIikKYGBgCgoKIVtdKHNoZWYtdm90ZXMucG5nKQotLS0tCgpOb3cgdG8gZGV0ZXJtaW5lIHRoZSB3aW5uZXJzIGZvciBlYWNoIGNvbnN0aXR1ZW5jeS4gVG8gZG8gdGhpcywgd2Ugd2lsbCBvcmRlciB0aGUgcGVyY2VudGFnZSBvZiB2b3RlcyB3aXRoaW4gZWFjaCBjb25zdGl0dWVuY3kgYnkgZmlyc3QgZ3JvdXBpbmcgd2l0aGluIGNvbnN0aXR1ZW5jeSBhbmQgdGhlbiB1c2luZyBgYXJyYW5nZWAuCgpgYGB7cn0KY2xlYW5lZCAlPiUgCiAgcGl2b3RfbG9uZ2VyKENvbnNlcnZhdGl2ZTpPdGhlciwgbmFtZXNfdG8gPSAiUGFydHkiLCB2YWx1ZXNfdG8gPSJWb3RlcyIpICU+JSAKICBmaWx0ZXIoIWlzLm5hKFZvdGVzKSkgJT4lIAogIG11dGF0ZShgUGVyY2VudGFnZSBvZiBWb3RlYCA9IChWb3RlcyAvIGBUb3RhbCB2b3Rlc2ApKjEwMCkgJT4lIAogIGdyb3VwX2J5KENvbnN0aXR1ZW5jeSkgJT4lIAogIGFycmFuZ2UoZGVzYyhgUGVyY2VudGFnZSBvZiBWb3RlYCksLmJ5X2dyb3VwID0gVFJVRSkKYGBgClRvIGNhbGN1bGF0ZSB0aGUgbWFqb3JpdHkgKGkuZS4gaG93IG11Y2ggZWFjaCBzZWF0IHdhcyB3b24gYnkpLCB3ZSdsbCBuZWVkIHRoZSBmaXJzdCBhbmQgc2Vjb25kIHBsYWNlIHZvdGVzLiBUaGUgYGxlYWRgIGZ1bmN0aW9uIGlzIHVzZWQgdG8gZmluZCB0aGUgc2Vjb25kIGhpZ2hlc3QgbnVtYmVyIG9mIHZvdGVzLgoKYGBge3J9CmNsZWFuZWQgJT4lIAogIHBpdm90X2xvbmdlcihDb25zZXJ2YXRpdmU6T3RoZXIsIG5hbWVzX3RvID0gIlBhcnR5IiwgdmFsdWVzX3RvID0iVm90ZXMiKSAlPiUgCiAgZmlsdGVyKCFpcy5uYShWb3RlcykpICU+JSAKICBtdXRhdGUoYFBlcmNlbnRhZ2Ugb2YgVm90ZWAgPSAoVm90ZXMgLyBgVG90YWwgdm90ZXNgKSoxMDApICU+JSAKICBncm91cF9ieShDb25zdGl0dWVuY3kpICU+JSAKICBhcnJhbmdlKGRlc2MoYFBlcmNlbnRhZ2Ugb2YgVm90ZWApLC5ieV9ncm91cCA9IFRSVUUpICU+JSAKICBzbGljZSgxOjIpICU+JSAKICBtdXRhdGUoYFNlY29uZCBQbGFjZWAgPSBsZWFkKGBQZXJjZW50YWdlIG9mIFZvdGVgLG9yZGVyX2J5ID0gQ29uc3RpdHVlbmN5KSwgTWFqb3JpdHkgPSBgUGVyY2VudGFnZSBvZiBWb3RlYCAtIGBTZWNvbmQgUGxhY2VgKQpgYGAKCkZpbmFsbHksIHdlIGNhbiBzZWxlY3QgdGhlIHdpbm5lciBvZiBlYWNoIHNlYXQgYW5kIHRoZWlyIG1ham9yaXR5LgoKYGBge3J9CnJlc3VsdHMgPC0gY2xlYW5lZCAlPiUgCiAgcGl2b3RfbG9uZ2VyKENvbnNlcnZhdGl2ZTpPdGhlciwgbmFtZXNfdG8gPSAiUGFydHkiLCB2YWx1ZXNfdG8gPSJWb3RlcyIpICU+JSAKICBmaWx0ZXIoIWlzLm5hKFZvdGVzKSkgJT4lIAogIG11dGF0ZShgUGVyY2VudGFnZSBvZiBWb3RlYCA9IChWb3RlcyAvIGBUb3RhbCB2b3Rlc2ApKjEwMCkgJT4lIAogIGdyb3VwX2J5KENvbnN0aXR1ZW5jeSkgJT4lIAogIGFycmFuZ2UoZGVzYyhgUGVyY2VudGFnZSBvZiBWb3RlYCksLmJ5X2dyb3VwID0gVFJVRSkgJT4lIAogIHNsaWNlKDE6MikgJT4lIAogIG11dGF0ZShgU2Vjb25kIFBsYWNlYCA9IGxlYWQoYFBlcmNlbnRhZ2Ugb2YgVm90ZWAsb3JkZXJfYnkgPSBDb25zdGl0dWVuY3kpLCBNYWpvcml0eSA9IGBQZXJjZW50YWdlIG9mIFZvdGVgIC0gYFNlY29uZCBQbGFjZWApICU+JSAKICBzbGljZSgxKQpgYGAKCkRpZCBzb21lIHBhcnRpZXMgd2luIGJ5IHNsaW1tZXIgbWFqb3JpdGllcz8KCmBgYHtyfQpyZXN1bHRzICU+JSAKICBmaWx0ZXIoUGFydHkgJWluJSBjKCJDb25zZXJ2YXRpdmUiLCAiR3JlZW4iLCAiTGFib3VyIiwgIkxpYi4gRGVtLiIsIlNOUCIpKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gUGFydHksIHkgPSBNYWpvcml0eSxmaWxsPVBhcnR5KSkgKyBnZW9tX2JveHBsb3QoKSArIGdlb21faml0dGVyKHdpZHRoPTAuMSkgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygiIzAwODdEQyIsICIjMDA4MDY2IiwiI0RDMjQxZiIsIiNGREJCMzAiLCIjRkZGRjAwIikpCmBgYAoKCldlIGNhbiBub3cgd29yayBvdXQgdGhlIG51bWJlciBvZiBzZWF0cwoKYGBge3J9CnNlYXRzXzIwMTkgPC0gcmVzdWx0cyAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBjb3VudChQYXJ0eSkgJT4lIAogIHJlbmFtZShTZWF0cyA9IG4pCndyaXRlX2NzdihzZWF0c18yMDE5LCAiMjAxOV9zZWF0cy5jc3YiKQpgYGAKCmBgYHtyfQpzZWF0c18yMDE5ICU+JSAKICBmaWx0ZXIoUGFydHkgJWluJSBjKCJDb25zZXJ2YXRpdmUiLCAiR3JlZW4iLCAiTGFib3VyIiwgIkxpYi4gRGVtLiIsIlNOUCIpKSAlPiUgCmdncGxvdChhZXMoeCA9IGZjdF9yZW9yZGVyKFBhcnR5LCBTZWF0cykseSA9IFNlYXRzLGZpbGw9UGFydHkpKSArIGdlb21fY29sKCkgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YygiIzAwODdEQyIsICIjMDA4MDY2IiwiI0RDMjQxZiIsIiNGREJCMzAiLCIjRkZGRjAwIikpICsgeGxhYigiUGFydHkiKSArIHlsYWIoIk51bWJlciBvZiBTZWF0cyIpCmBgYAoK